package com.mcarthur.countdown;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.Stack;

import javax.microedition.khronos.opengles.GL10;

/**
 * Manages balls :-)
 */
public class BouncyBallManager {
	private static final int NUM_BALLS = 50;
	private static final float MAX_VEL = 0.5f; //10
	private static final float GRAVITY = 0.001f;  //.8f
	private static final float KINETIC_DAMPER = -0.9f;
	private static final float KINETIC_DAMPER_X = 0.96f;
	
	private static BouncyBallManager instance;
	
	private Queue<BouncyBall> activeBalls;
	private Stack<BouncyBall> inactiveBalls;
	
	// Singleton
	private BouncyBallManager() {
		activeBalls = new LinkedList<BouncyBall>();
		inactiveBalls = new Stack<BouncyBall>();
		for (int i = 0; i < NUM_BALLS; i++) {
			inactiveBalls.add(new BouncyBall());
		}
	}
	
	public static BouncyBallManager get() {
		if (instance == null) {
			instance = new BouncyBallManager();
		}
		return instance;
	}
	
	/**
	 * Activates a fresh bouncy ball.
	 */
	public void activateBouncyBall(Ball parentBall) {
		BouncyBall activeBall = null;
		
		if (!inactiveBalls.isEmpty()) {
			activeBall = inactiveBalls.pop();
			activeBalls.add(activeBall);
		} else {
			activeBall = activeBalls.remove();
			activeBalls.add(activeBall);
		}
		
		activeBall.setActive(true);
		activeBall.setPosX(parentBall.getPosX());
		activeBall.setPosY(parentBall.getPosY());
		activeBall.setActiveColor(parentBall.getActiveColor());
		
		randomize(activeBall);
	}
	
	private long drawTime;
	
	public void draw(GL10 gl) {
		long time = System.currentTimeMillis();
		if (drawTime == 0) {
			drawTime  = time;
		}
		
		long dt =  time - drawTime;
		drawTime = time;
		
		//TODO: need to incorporate timestep!!!
		for (Iterator<BouncyBall> it = activeBalls.iterator(); it.hasNext(); ) {
			BouncyBall ball = it.next();
			
			if (ball.getPosX() <= -Ball.WIDTH || ball.getPosX() >= CountdownRenderer.viewWidth) {
				it.remove();
				inactiveBalls.push(ball);
				continue;
			}
			
			ball.setVelY(ball.getVelY() - (GRAVITY * dt));
			
			ball.setPosX(ball.getPosX() + (ball.getVelX() * dt));
			ball.setPosY(ball.getPosY() + (ball.getVelY() * dt));
						
			checkBounds(ball);
			checkCollision(ball);

			ball.draw(gl);
		}

//		for (BouncyBall ball : activeBalls) {
//			ball.setVelY(ball.getVelY() - GRAVITY);
//			
//			ball.setPosX(ball.getPosX() + ball.getVelX());
//			ball.setPosY(ball.getPosY() + ball.getVelY());
//						
//			checkBounds(ball);
//			checkCollision(ball);
//
//			ball.draw(gl);
//		}
	}
	
	public void onResume() {
		drawTime = 0;
	}
	
	private void checkBounds(BouncyBall ball) {
		// Concurrent mod exception ;-( might not be needed tho
		if (ball.getPosX() <= -Ball.WIDTH || ball.getPosX() >= CountdownRenderer.viewWidth) {
//			activeBalls.remove(ball);
//			inactiveBalls.push(ball);
//			return;
		}
		if (ball.getPosY() < 0) {
			//ball.setPosY(ball.getPosY() - ball.getVelY());
			ball.setPosY(0.f);
			ball.setVelY(ball.getVelY() * KINETIC_DAMPER);
			ball.setVelX(ball.getVelX() * KINETIC_DAMPER_X);
			

//			Vector2 old = new Vector2(ball.getVelX(), ball.getVelY()).getUnit();
//			Vector2 normal = new Vector2(0.f, -1.f);
//			Vector2 vNew = normal.mult(2 * (old.mult(-1.f).dot(normal))).add(old);
//			ball.setVelX(vNew.x);
//			ball.setVelY(vNew.y);
		}

		
//		R is the new direction vector
//		I is the old direction vector before the collision
//		N is the Normal at the collision point 
//
//		The new vector R is calculated as follows: 
//
//		R= 2*(-I dot N)*N + I 
//
//		The restriction is that the I and N vectors have to be unit vectors
	}
	
	private void checkCollision(BouncyBall ball) {
		
		// BEGIN INSANE PHYSICS
		
		for (BouncyBall other : activeBalls) {
			if (ball == other) {
				continue;
			}
			
			Vector2 axis = ball.getCenter().sub(other.getCenter());
			if (axis.getMagnitude() < (Ball.WIDTH)) {
				// collision!
				axis = axis.getUnit();
				
				Vector2 b1 = new Vector2(ball.getVelX(), ball.getVelY());
				Vector2 b1x = axis.mult((axis.dot(b1)));
				Vector2 b1y = b1.sub(b1x);
				
				Vector2 b2 = new Vector2(other.getVelX(), other.getVelY());
				Vector2 negAxis = axis.mult(-1);
				Vector2 b2x = negAxis.mult(negAxis.dot(b2));
				Vector2 b2y = b2.sub(b2x);
				
				Vector2 v1x = (b2x.mult(2)).mult(0.5f);
				Vector2 v2x = (b1x.mult(2)).mult(0.5f);
				
				Vector2 v1y = b1y;
				Vector2 v2y = b2y;

				Vector2 v1 = v1x.add(v1y);
				Vector2 v2 = v2x.add(v2y);
				
				ball.setVelX(v1.x);
				ball.setVelY(v1.y);
				
				other.setVelX(v2.x);
				other.setVelY(v2.y);
			}
		}

		// END
	}

	private void randomize(BouncyBall ball) {
		Random random = new Random();
		float velX = random.nextFloat() * MAX_VEL;
		if (random.nextBoolean()) {
			velX = -velX;
		}
		float velY = random.nextFloat() * MAX_VEL;
		if (random.nextBoolean()) {
			velY = -velY;
		}
		
		ball.setVelX(velX);
		ball.setVelY(velY);
	}
}
